Longest Common Subsequence (LCS) using Dynamic Programming
The Longest Common Subsequence (LCS) problem is a classic dynamic programming problem that finds the longest subsequence common to two sequences.
Problem Statementβ
Given two sequences (X and Y), find the length of the longest common subsequence.
Intuitionβ
The LCS problem can be efficiently solved using dynamic programming. We can build a table to store the lengths of the longest common subsequences for various prefixes of the input sequences.
Naive Recursive Approachβ
The naive recursive approach to solving the LCS problem has exponential time complexity and is not efficient for large inputs.
def lcs_recursive(X, Y, m, n):
if m == 0 or n == 0:
return 0
elif X[m - 1] == Y[n - 1]:
return 1 + lcs_recursive(X, Y, m - 1, n - 1)
else:
return max(lcs_recursive(X, Y, m, n - 1), lcs_recursive(X, Y, m - 1, n))
Dynamic Programming Approachβ
Using dynamic programming, we can efficiently solve the LCS problem by building a table and filling it iteratively.
Bottom-Up Approachβ
We iteratively fill the table to find the length of the longest common subsequence.
Pseudocode for LCS using DPβ
Initialize:β
Let m be the length of sequence X
Let n be the length of sequence Y
Create a table dp[m + 1][n + 1] to store lengths of LCS
Loop from 0 to m:
Set dp[i][0] = 0
Loop from 0 to n:
Set dp[0][j] = 0
Loop from 1 to m:
Loop from 1 to n:
If X[i-1] == Y[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
Else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
Return dp[m][n] which represents the length of the LCS
Example Output:β
Given the input sequences:
X = "AGGTAB"
Y = "GXTXAYB"
The length of the Longest Common Subsequence (LCS) between X
and Y
is 4
.
Output Explanation:β
The Longest Common Subsequence (LCS) between X
and Y
is "GTAB", which has a length of 4
.
You can verify this by manually checking the common subsequences:
- "G", "G", "G", "A", "A", "B"
- "G", "G", "T", "A", "A", "B"
- "G", "X", "T", "A", "A", "B"
- "A", "G", "T", "A", "A", "B"
- "A", "X", "T", "A", "A", "B"
- "A", "G", "X", "A", "A", "B"
- "A", "G", "T", "X", "A", "B"
- "A", "G", "T", "A", "X", "B"
- "A", "G", "T", "A", "A", "X"
Out of these, "GTAB" is the longest common subsequence with a length of 4
, hence the output.
Implementing LCS using DPβ
Python Implementationβ
def lcs_dp(X, Y):
# Get lengths of input sequences
m, n = len(X), len(Y)
# Initialize a table to store lengths of LCS
dp = [[0] * (n + 1) for _ in range(m + 1)]
# Iterate through the sequences to fill the table
for i in range(1, m + 1):
for j in range(1, n + 1):
# If characters match, increment LCS length
if X[i - 1] == Y[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
# Otherwise, take the maximum of previous LCS lengths
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
# Return the length of LCS
return dp[m][n]
Java Implementationβ
public class LCS {
public static int lcs_dp(String X, String Y) {
// Get lengths of input sequences
int m = X.length();
int n = Y.length();
// Initialize a table to store lengths of LCS
int[][] dp = new int[m + 1][n + 1];
// Iterate through the sequences to fill the table
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// If characters match, increment LCS length
if (X.charAt(i - 1) == Y.charAt(j - 1))
dp[i][j] = dp[i - 1][j - 1] + 1;
else
// Otherwise, take the maximum of previous LCS lengths
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
// Return the length of LCS
return dp[m][n];
}
public static void main(String[] args) {
// Example usage
String X = "AGGTAB";
String Y = "GXTXAYB";
System.out.println("Length of LCS: " + lcs_dp(X, Y));
}
}
C++ Implementationβ
#include <iostream>
#include <string>
using namespace std;
int lcs_dp(string X, string Y) {
// Get lengths of input sequences
int m = X.length();
int n = Y.length();
// Initialize a table to store lengths of LCS
int dp[m + 1][n + 1];
// Iterate through the sequences to fill the table
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
// Base case: if either sequence is empty, LCS length is 0
if (i == 0 || j == 0)
dp[i][j] = 0;
// If characters match, increment LCS length
else if (X[i - 1] == Y[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
// Otherwise, take the maximum of previous LCS lengths
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
// Return the length of LCS
return dp[m][n];
}
int main() {
// Example usage
string X = "AGGTAB";
string Y = "GXTXAYB";
cout << "Length of LCS: " << lcs_dp(X, Y) << endl;
return 0;
}
JavaScript Implementationβ
function lcs_dp(X, Y) {
// Get lengths of input sequences
const m = X.length;
const n = Y.length;
// Initialize a table to store lengths of LCS
const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
// Iterate through the sequences to fill the table
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
// If characters match, increment LCS length
if (X[i - 1] === Y[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
// Otherwise, take the maximum of previous LCS lengths
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
// Return the length of LCS
return dp[m][n];
}
// Example usage
const X = "AGGTAB";
const Y = "GXTXAYB";
console.log("Length of LCS: " + lcs_dp(X, Y));
Complexity Analysisβ
- Time Complexity: , where m and n are the lengths of the input sequences X and Y, respectively.
- Space Complexity: , due to the table used for dynamic programming.
Conclusionβ
Dynamic programming provides an efficient solution for finding the longest common subsequence between two sequences by storing intermediate results and avoiding redundant calculations. This approach significantly reduces the time complexity compared to the naive recursive approach.